Skip to content

Conversation

@sungshik
Copy link
Contributor

@sungshik sungshik commented Apr 21, 2025

This PR adds the low-level JNA code to use native macOS APIs. See the commit messages for details.

Note: Only the functions of the APIs that we need are mapped -- not everything.

@sungshik sungshik mentioned this pull request Apr 21, 2025
3 tasks
@codecov
Copy link

codecov bot commented Apr 21, 2025

Codecov Report

Attention: Patch coverage is 0% with 137 lines in your changes missing coverage. Please review.

Project coverage is 64.9%. Comparing base (cdc0b70) to head (d17f342).
Report is 35 commits behind head on improved-macos-support-main.

Files with missing lines Patch % Lines
...neering/swat/watch/impl/mac/NativeEventStream.java 0.0% 90 Missing ⚠️
...ing/swat/watch/impl/mac/apis/FileSystemEvents.java 0.0% 45 Missing ⚠️
...ring/swat/watch/impl/mac/apis/DispatchObjects.java 0.0% 1 Missing ⚠️
...eering/swat/watch/impl/mac/apis/DispatchQueue.java 0.0% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (0.0%) is below the target coverage (50.0%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@                       Coverage Diff                        @@
##             improved-macos-support-main     #40      +/-   ##
================================================================
- Coverage                           80.5%   64.9%   -15.6%     
- Complexity                           134     135       +1     
================================================================
  Files                                 16      20       +4     
  Lines                                570     705     +135     
  Branches                              66      80      +14     
================================================================
- Hits                                 459     458       -1     
- Misses                                69     206     +137     
+ Partials                              42      41       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sungshik sungshik marked this pull request as ready for review April 22, 2025 07:20
@sungshik sungshik requested a review from DavyLandman April 22, 2025 07:20
Copy link
Member

@DavyLandman DavyLandman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small comments.

Impressive work.

// Native memory (automatically deallocated when set to `null`)
private @Nullable FSEventStreamCallback callback;
private @Nullable Pointer stream;
private @Nullable Pointer queue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they should be volatile, as their value will get ready by multiple threads, and you don't want thread-local caches. Since they could point towards bad pointers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reads/writes to these fields are inside synchronized blocks. Comment added to clarify.

private static final DispatchQueue DQ = DispatchQueue.INSTANCE;
private static final FileSystemEvents FSE = FileSystemEvents.INSTANCE;

// Native memory (automatically deallocated when set to `null`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who takes care of that deallocation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is a bit confusing. The main point it was supposed to convey is that the callback field is needed to keep a live reference to the callback (otherwise, it's prematurely GC'ed). Comment rephrased.

(To also answer the question: stream and queue are deallocated explicitly in method close() through invocations of FSE.FSEventStreamRelease and DO.dispatch_release. See also another comment below.)

private volatile boolean closed;

public NativeEventStream(Path path, NativeEventHandler handler) throws IOException {
this.path = path.toRealPath(); // Resolve symbolic links
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we also store the original requested path? So that our events generated are in the context of the original path, and not the resolved path?

or otherwise, we should make it an argument requirement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All paths returned to consumers of the stream are relative to the root of the watch scope (so they can be used as contexts in WatchEvents). However, macOS always seems to be using real paths in events, so to be able to relativize, the real path of the root is needed.

For instance, the original path could be this (i.e., new TestDirectory().getTestDirectory()):

/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313

But, the paths in the incoming events are these:

/private/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313/a.txt
/private/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313/b.txt
/private/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313/d.txt

So, we cannot relativize to the original path. Instead, we need to compute path.toRealPath():

/private/var/folders/k3/fpm2wy9s6l16x1cchqr9k7wh0000gn/T/java-watch-test9831264884788018313

Then, we can relativize to path.toRealPath():

a.txt
b.txt
d.txt

closed = false;
}

// Allocate native memory. (Checker Framework: The local variables are
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these CF comments needed? CF should understand assignment like this, it knows that createCallback does not return a nullable result?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CF is a bit erratic about what it does, and doesn't, understand. (E.g., changing the order of the assignment to stream and queue confuses CF.) Anyway, I rewrote it a bit and removed the comment.

public void callback(Pointer streamRef, Pointer clientCallBackInfo,
long numEvents, Pointer eventPaths, Pointer eventFlags, Pointer eventIds) {
// This function is called each time native events are issued by
// macOS. The purpose of this function is to perform the minimal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be wrong, but it reads like we should make sure that this function does not consume a lot of time? As it's a native callback?

If that is true, I think we should publish the event to a ConcurrentQueue, as we don't know how much work happens inside of the handler

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative solution as discussed: make the class package private.

DO.dispatch_release(queue);
}

// Deallocate native memory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't this leave it to the GC/destroy to deallocate? isn't there an explicit function in the JNA framework we can call to cleanup?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment was misplaced. The actual deallocation of the stream and the queue happens in FSE.FSEventStreamRelease and DO.dispatch_release on the lines above; the deallocation of the callback does happen by setting it to null, though (so it can then be GC'ed). Moved comment.

@sungshik sungshik merged commit 73b0238 into improved-macos-support-main Apr 22, 2025
11 of 13 checks passed
@sungshik sungshik deleted the improved-macos-support/jna branch April 22, 2025 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants